<?php

namespace app\server;

class WxMpServer
{

    public $appid = '';
    public $appsecret = '';
    public $access_token = '';
    public $cache_path = '';

    /* 基础函数 */

    /**
     * 构造函数
     */
    public function __construct($app) {
        $this->appid = $app['appid'];
        $this->appsecret = $app['appsecret'];
        $this->cache_path = $app['cache_path'];
        $this->access_token = $this->getAccessToken();
    }

    /**
     * 检验数据的真实性，并且获取解密后的明文
     * @param string $session_key 用户在小程序登录后获取的会话密钥
     * @param string $encrypted_data 加密的用户数据
     * @param string $iv 与用户数据一同返回的初始向量
     * @param string $data 解密后的原文
     * @return string 成功0，失败返回对应的错误码
     */
    public function decryptData($session_key, $encrypted_data, $iv, &$data) {
        if (strlen($session_key) != 24) {
            return '-41001'; // encodingAesKey非法
        }
        $aes_key = base64_decode($session_key);
        if (strlen($iv) != 24) {
            return '-41002';
        }
        $aes_iv = base64_decode($iv);
        $aes_cipher = base64_decode($encrypted_data);
        $result = openssl_decrypt($aes_cipher, 'AES-128-CBC', $aes_key, 1, $aes_iv);
        $data_obj = json_decode($result);
        if ($data_obj == NULL) {
            return '-41003'; // aes解密失败
        }
        if ($data_obj->watermark->appid != $this->appid) {
            //return '-41003';
        }
        $data = $result;
        return '0';
    }

    /**
     * 将xml字符串格式化成数组
     * @param string $xml xml字符串
     * @return array 数组
     */
    public function formXml($xml) {
        $value = libxml_disable_entity_loader(true);
        $array = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        libxml_disable_entity_loader($value);
        return $array;
    }

    /**
     * 调用远程服务接口，获取文件流，post方式
     * @param string $url 接口地址
     * @param array $params 要发送的参数数组
     * @return 文件流
     */
    public function httpCopy($url, $params = array()) {
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检测
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Expect:')); //解决数据包大不能提交
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($params)); // Post提交的数据包
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
        $response = curl_exec($curl); // 执行操作
        if (curl_errno($curl)) { // 返回最后一次 cURL 操作的错误代码
            exit(curl_error($curl)); // 返回当前会话最后一次错误的字符串
        }
        curl_close($curl); // 关闭CURL会话
        return $response; // 返回数据
    }

    /**
     * 调用远程服务接口，get方式
     * @param string $url 接口地址，并包含尾随的参数
     * @return array 接口返回数据
     */
    public function httpGet($url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $response = curl_exec($ch);
        curl_close($ch);
        $data = json_decode($response, true);
        return $data;
    }

    /**
     * 调用远程服务接口，post方式
     * @param string $url 接口地址
     * @param array $params 要发送的参数数组
     * @param bool $encode 参数数组是否要转换成json格式字符串（默认要转换）
     * @return array 接口返回数据
     */
    public function httpPost($url, $params = array(), $encode = true) {
        if (is_array($params)) {
            if ($encode) {
                $post_data = json_encode($params);
            } else {
                $post_data = $params;
            }
        } else {
            $post_data = $params;
        }
        try {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
            $response = curl_exec($ch);
            curl_close($ch);
            $data = json_decode($response, true);
            return $data;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * 将带有中文字符的数组编码格式化成json
     * @param array $array 要格式化的数组
     * @return string json格式字符串
     */
    public function jsonEncode($array) {
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            $str = json_encode($array);
            $str = preg_replace_callback("#\\\u([0-9a-f]{4})#i", function($matchs) {
                return iconv('UCS-2BE', 'UTF-8', pack('H4', $matchs[1]));
            }, $str);
            return $str;
        } else {
            return json_encode($array, JSON_UNESCAPED_UNICODE);
        }
    }

    /**
     * 日志记录
     * @param string $str 日志信息
     * @param string $name 日志名称
     */
    public function logs($str = '', $name = '') {
        $handle = fopen(dirname(__DIR__) . '/logs/' . date('Y-m-d') . '_' . $name . '.txt', 'a+');
        if ($handle) {
            fwrite($handle, '[' . date('Y-m-d H:i:s') . ']' . "\r\n" . $str . "\r\n");
            fclose($handle);
        }
    }

    /**
     * 将请求参数格式化成url参数
     * @param array $params 请求参数
     * @return string url参数字符串
     */
    public function toUrlParams($params) {
        $buff = '';
        foreach ($params as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        $buff = trim($buff, '&');
        return $buff;
    }

    /**
     * 站点token检验
     * @param type $token Token可由开发者可以任意填写，用作生成签名（该Token会和接口URL中包含的Token进行比对，从而验证安全性）
     */
    public function tokenVerify($token) {
        // 接收微信公众平台发送过来的参数
        $signature = $_GET['signature']; // 微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        $timestamp = $_GET['timestamp']; // 时间戳
        $nonce = $_GET['nonce']; // 随机数
        // 几个参数赋值到数组，然后按字典序排序
        $arr = array($timestamp, $nonce, $token);
        sort($arr, SORT_STRING);
        // 拼接成字符串，sha1加密，然后与signature进行校验
        $sign = sha1(implode($arr));
        // 设置微信公众平台服务器地址(URL)时使用，仅使用一次
        if (isset($_GET['echostr']) && $sign == $signature) {
            echo $_GET['echostr']; // 随机字符串
            exit();
        }
        // 接收微信公众平台发送过来的信息，验证其合法性
        if ($sign != $signature) {
            exit();
        }
    }

    /**
     * 将请求参数格式化成xml
     * @param array $params 请求参数
     * @return string xml字符串
     */
    public function toXml($params) {
        $xml = "<xml>";
        foreach ($params as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /* 接口函数 */

    /**
     * 获取access_token，日调用2000次，过期时间7200秒
     * 接口文档：https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
     * @return string access_token
     */
    public function getAccessToken() {
        // 读取缓存
        $cache = json_decode(trim(substr(file_get_contents($this->cache_path), 15)), true);
        // 判断缓存是否超时
        if ($cache['expire_time'] < time()) {
            // 1、缓存超时重新获取
            // 接口参数
            $params = array(
                'grant_type' => 'client_credential',
                'appid' => $this->appid,
                'secret' => $this->appsecret
            );
            // 接口地址
            $url = 'https://api.weixin.qq.com/cgi-bin/token?' . $this->toUrlParams($params);
            // 调用接口
            $data = $this->httpGet($url);
            if (isset($data['access_token'])) {
                $access_token = $data['access_token'];
                // 写入缓存
                $_cache['expire_time'] = time() + 7000;
                $_cache['access_token'] = $access_token;
                $fp = fopen($this->cache_path, 'w');
                fwrite($fp, "<?php exit();?>" . json_encode($_cache));
                fclose($fp);
            } else {
                // 输出错误信息
                exit($data['errmsg']);
            }
        } else {
            // 2、缓存未超时直接输出
            $access_token = $cache['access_token'];
        }
        return $access_token;
    }

    /**
     * 发送公众号客服消息
     * 接口文档：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html
     * @param array $params 请求参数
     * @return array 接口返回数据
     */
    public function sendCustomMsg($params) {
        // 接口地址
        $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' . $this->access_token;
        // 调用接口
        $json = $this->jsonEncode($params);
        $data = $this->httpPost($url, $json);
        return $data;
    }

    /**
     * 发送公众号模板消息
     * 接口文档：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
     * @param array $params 请求参数
     * @return array 接口返回数据
     */
    public function sendTemplateMsg($params) {
        // 接口地址
        $url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $this->access_token;
        // 调用接口
        $json = $this->jsonEncode($params);
        $data = $this->httpPost($url, $json);
        return $data;
    }
}


?>